import json
import struct
from copyreg import constructor

import util.binary_reader
import jvm8_consts
from jvm8_consts import ConstantPoolTags


class JavaClassError(BaseException) : ...

class JavaClassEater:
    def __init__(self, filepath : str):
        self.br = util.binary_reader.BinaryReader(filepath)
        self.class_file = {}
        self.constant_pool_map = jvm8_consts.get_value_to_enum_from_constant_pool_tags()

    def eat_all(self):
        self._eat_header()
        constant_pool = {}
        for i in range(1, self.class_file['constant_pool_count']):   # 0 < constant_pool_index < constant_pool_count
            constant_pool[i] = self._eat_constant_pool()  # constant pool index starts from 1

        self.class_file['constant_pool'] = constant_pool
        return self.class_file

    def print_pretty(self):
        if len(self.class_file) == 0:
            print('no data available')
            return
        # print header
        self._print_handler('major version: %d' % self.class_file['major_version'])
        self._print_handler('minor version: %d' % self.class_file['minor_version'])
        # constant pool
        for i in range(1, self.class_file['constant_pool_count']):
            self._print_constant_recursive(i)

    def _print_constant_recursive(self, cp_index : int, layer = 0):
        # idx
        idx_prefix = 'idx=%d  ' % cp_index
        # tag
        constant_struct = self.class_file['constant_pool'][cp_index]
        tag = constant_struct['tag']
        tag_name = constant_struct['tag_name']
        if isinstance(tag_name, ConstantPoolTags):
            tag_name = str(tag_name)
        self._print_handler(idx_prefix + tag_name, padding_multi=layer)
        if   tag == ConstantPoolTags.CONSTANT_Class.value:
            self._print_handler('name index=', padding_multi=layer+1)
            self._print_constant_recursive(constant_struct['name_index'], layer=layer+1)
        elif tag in [ConstantPoolTags.CONSTANT_Fieldref.value, ConstantPoolTags.CONSTANT_Methodref.value, ConstantPoolTags.CONSTANT_InterfaceMethodref.value]:
            self._print_handler('class index=', padding_multi=layer+1)
            self._print_constant_recursive(constant_struct['class_index'], layer=layer+1)
            self._print_handler('name and type index=', padding_multi=layer+1)
            self._print_constant_recursive(constant_struct['name_and_type_index'], layer+layer+1)
        elif tag == ConstantPoolTags.CONSTANT_String.value:
            self._print_handler('string index=', padding_multi=layer+1)
            self._print_constant_recursive(constant_struct['string_index'], layer=layer+1)
        elif tag == ConstantPoolTags.CONSTANT_Integer.value:
            self._print_handler('value= %d' % constant_struct['value'], padding_multi=layer+1)
        elif tag == ConstantPoolTags.CONSTANT_Float.value:
            self._print_handler('value= %f' % constant_struct['value'], padding_multi=layer+1)
        elif tag == ConstantPoolTags.CONSTANT_Long.value:
            self._print_handler('value= %d' % constant_struct['value'], padding_multi=layer+1)
        elif tag == ConstantPoolTags.CONSTANT_Double.value:
            self._print_handler('value= %f' % constant_struct['value'], padding_multi=layer+1)
        elif tag == ConstantPoolTags.CONSTANT_NameAndType.value:
            self._print_handler('name index=', padding_multi=layer+1)
            self._print_constant_recursive(constant_struct['name_index'], layer=layer+1)
            self._print_handler('descriptor index=', padding_multi=layer+1)
            self._print_constant_recursive(constant_struct['descriptor_index'], layer=layer+1)
        elif tag == ConstantPoolTags.CONSTANT_Utf8.value:
            self._print_handler('value= "%s"' % constant_struct['value'], padding_multi=layer+1)
        elif tag == ConstantPoolTags.CONSTANT_MethodHandle.value:
            self._print_handler('reference kind= %d' % constant_struct['reference_kind'], padding_multi=layer+1)
            self._print_handler('reference index= ', padding_multi=layer+1)
            self._print_constant_recursive(constant_struct['reference_index'], layer=layer+1)
        elif tag == ConstantPoolTags.CONSTANT_MethodType.value:
            self._print_handler('descriptor index=', padding_multi=layer+1)
            self._print_constant_recursive(constant_struct['descriptor_index'], layer=layer+1)
        elif tag == ConstantPoolTags.CONSTANT_InvokeDynamic.value:
            self._print_handler('bootstrap method attr index=', padding_multi=layer+1)
            self._print_constant_recursive(constant_struct['bootstrap_method_attr_index'], layer=layer+1)
            self._print_handler('name and type index=', padding_multi=layer+1)
            self._print_constant_recursive(constant_struct['name_and_type_index'], layer=layer+1)
        else:
            raise JavaClassError('no matching enum for %d' % tag)

    def _print_handler(self, message, padding_multi = 0):
        print('  ' * padding_multi + message)

    def _eat_header(self):
        if self.br.read_u4() != jvm8_consts.MAGIC_NUMBER:
            raise JavaClassError('wrong magic number. input file may not be a valid class file.')
        self.class_file = {}
        self.class_file['magic'] = jvm8_consts.MAGIC_NUMBER.hex(sep=' ')
        self.class_file['minor_version'] = int.from_bytes(self.br.read_u2(), 'big')
        self.class_file['major_version'] = int.from_bytes(self.br.read_u2(), 'big')
        self.class_file['constant_pool_count'] = int.from_bytes(self.br.read_u2(), 'big')

    def _eat_constant_pool(self):
        # print(self.br.tell())
        tag = int.from_bytes(self.br.read_u1(), 'big')
        # tag_name 与 value 字段是新增的，非原始字段。为了方便查看和取值。
        info = {'tag' : self.constant_pool_map[tag].value, 'tag_name' : self.constant_pool_map[tag]}
        # print(info)
        if   tag == ConstantPoolTags.CONSTANT_Class.value:
            info['name_index'] = self.br.read_short()
        elif tag in [ConstantPoolTags.CONSTANT_Fieldref.value, ConstantPoolTags.CONSTANT_Methodref.value, ConstantPoolTags.CONSTANT_InterfaceMethodref.value]:
            info['class_index'] = self.br.read_short()
            info['name_and_type_index'] = self.br.read_short()
        elif tag == ConstantPoolTags.CONSTANT_String.value:
            info['string_index'] = self.br.read_short()
        elif tag == ConstantPoolTags.CONSTANT_Integer.value:
            t = self.br.read_u4()
            info['bytes'] = t.hex()
            info['value'] = struct.unpack('>i', t)[0]
        elif tag == ConstantPoolTags.CONSTANT_Float.value:
            t = self.br.read_u4()
            info['bytes'] = t.hex()
            info['value'] = struct.unpack('>f', t)[0]
        elif tag == ConstantPoolTags.CONSTANT_Long.value:
            t_high = self.br.read_u4()
            t_low = self.br.read_u4()
            info['high_bytes'] = t_high.hex()
            info['low_bytes'] = t_low.hex()
            info['value'] = struct.unpack('>l', t_high+t_low)[0]
        elif tag == ConstantPoolTags.CONSTANT_Double.value:
            t_high = self.br.read_u4()
            t_low = self.br.read_u4()
            info['high_bytes'] = t_high.hex()
            info['low_bytes'] = t_low.hex()
            info['value'] = struct.unpack('>d', t_high+t_low)[0]
        elif tag == ConstantPoolTags.CONSTANT_NameAndType.value:
            info['name_index'] = self.br.read_short()
            info['descriptor_index'] = self.br.read_short()
        elif tag == ConstantPoolTags.CONSTANT_Utf8.value:
            info['length'] = self.br.read_short()
            t = self.br.read_n_bytes(info['length'])
            info['bytes'] = t.hex()
            info['value'] = t.decode()
        elif tag == ConstantPoolTags.CONSTANT_MethodHandle.value:
            info['reference_kind'] = self.br.read_u1()[0]
            info['reference_index'] = self.br.read_short()
        elif tag == ConstantPoolTags.CONSTANT_MethodType.value:
            info['descriptor_index'] = self.br.read_short()
        elif tag == ConstantPoolTags.CONSTANT_InvokeDynamic.value:
            info['bootstrap_method_attr_index'] = self.br.read_short()
            info['name_and_type_index'] = self.br.read_short()
        else:
            raise JavaClassError('no matching enum for %d' % tag)
        return info


def main():
    # ref = r'H:\dev\temp_dir\SampleConsts.class'
    ref = r'H:\dev\temp_dir\AuxiliarySupport.class'

    eater = JavaClassEater(ref)

    result = eater.eat_all()
    print(result)
    json.dump(result, fp=open('dump.json', 'w'), indent=2, cls=jvm8_consts.ConstantPoolTagsEncoder)

    eater.print_pretty()

if __name__ == "__main__":
    main()